Skip to content

docs(rfc): Vite+ Project Detection for Editor Extensions#1614

Draft
fengmk2 wants to merge 4 commits into
mainfrom
vp-detect
Draft

docs(rfc): Vite+ Project Detection for Editor Extensions#1614
fengmk2 wants to merge 4 commits into
mainfrom
vp-detect

Conversation

@fengmk2
Copy link
Copy Markdown
Member

@fengmk2 fengmk2 commented May 18, 2026

Summary

Draft RFC defining how the four oxc editor extensions — oxc-vscode, oxc-zed, oxc-intellij-plugin, coc-oxc — determine whether a workspace is a Vite+ project and which vp executable to spawn for --lsp. This is the prerequisite identified in #1557 for removing the per-package bin/oxlint / bin/oxfmt wrappers.

Core rule

A workspace is Vite+ iff some walked-up package.json directly declares vite-plus in dependencies or devDependencies (bounded by the workspace root). The runnable vp binary is resolved separately, also bounded by the workspace root, and validated against a real vite-plus package (name === "vite-plus").

Detector returns a tri-state:

  • null — not Vite+; editor uses plain oxlint/oxfmt.
  • { root, vpPath } — Vite+ and runnable; editor launches vp lint --lsp / vp fmt --lsp.
  • { root } — declared but not yet installed (fresh clone, Berry PnP, broken install); editor falls back to plain oxlint/oxfmt and never spawns a bare vp.

A transitively hoisted node_modules/vite-plus/, a global vp, a vp on \$PATH, and a configured oxc.<tool>.binPath all return null by construction.

Distribution

  • oxc-vscode and coc-oxc consume a shared bundled-devDependency npm package (proposed @voidzero-dev/detect-vite-plus) at packages/detect-vite-plus/.
  • oxc-zed (Rust/WASM) and oxc-intellij-plugin (Kotlin) port the same algorithm against the shared conformance fixtures inside this RFC.

Status

Draft for discussion. Open points called out in the RFC:

  • Final package name.
  • Caching policy in editor consumers.
  • "Declared but not installed" UX (silent fallback vs. install hint).
  • Workspace-root marker parity gap with vite-task (recognized as a known follow-up; deliberately not blocked on).

Refs #1557

Defines a portable rule the four oxc editor extensions
(oxc-vscode, oxc-zed, oxc-intellij-plugin, coc-oxc) use to decide
whether to launch `vp lint --lsp` / `vp fmt --lsp` instead of plain
oxlint / oxfmt, and to resolve which executable to spawn.

Core algorithm (two phases, both bounded by the workspace root):

1. Phase 1 — find the package.json that DIRECTLY declares vite-plus
   in dependencies or devDependencies. A transitively hoisted
   `node_modules/vite-plus/` does not count.
2. Phase 2 — walk up from the declaring ancestor looking for a real
   `node_modules/vite-plus/bin/vp` with a sibling `package.json` that
   parses and has `name === "vite-plus"`.

Result is a tri-state: `null` (not Vite+), `{ root, vpPath }`
(Vite+ and runnable), or `{ root }` (declared but not yet installed —
editors fall back to plain oxlint/oxfmt and never launch a bare vp).

Distribution: for the Node-capable extensions (oxc-vscode, coc-oxc)
the detector ships as a shared bundled-devDependency package
(@voidzero-dev/detect-vite-plus, name TBD) at
`packages/detect-vite-plus/`. oxc-zed (Rust) and
oxc-intellij-plugin (Kotlin) port the same algorithm against the
shared conformance fixtures.

The RFC also documents:
- The four extensions' existing bin-resolution chains with code
  excerpts and file:line citations.
- A Mermaid flowchart of the two-phase algorithm for cross-language
  porters.
- The workspace-root marker set (`pnpm-workspace.yaml`,
  `package.json#workspaces`, `lerna.json`) and a known parity gap
  with vite-task that this RFC does not block on.
- Per-extension migration plans and a conformance fixture table that
  pins down behaviour across every implementation.

Refs #1557
@fengmk2 fengmk2 self-assigned this May 18, 2026
@netlify
Copy link
Copy Markdown

netlify Bot commented May 18, 2026

Deploy Preview for viteplus-preview canceled.

Name Link
🔨 Latest commit 8a66ee0
🔍 Latest deploy log https://app.netlify.com/projects/viteplus-preview/deploys/6a0a8d2e4ab3c8000875dc6d

fengmk2 added 3 commits May 18, 2026 11:02
… isn't

.vp-root was a fabricated example. Replace with .vite-hooks, which
is the real vite-plus hooks directory (packages/cli/src/config/hooks.ts:67)
and a more useful illustration of "incidental vite-plus artifact that
must not stop the walk-up."

Refs #1557
The detector now reads node_modules/vite-plus/package.json#version
alongside name validation and returns it in DetectResult as
vpVersion. Editors compare against MIN_VP_VERSION_FOR_LSP (a constant
exported by the shared detector package, filled in when #1557's
wrapper removal lands) to choose between launching `vp lint --lsp`
and falling through to the legacy bin/oxlint wrapper chain shipped
by older vite-plus versions.

Three benefits:
- Smooth rollout: workspaces pinned to older vite-plus keep working
  via the existing wrapper, no breakage on upgrade of the editor
  extension alone.
- Upgrade hint: editors can surface "upgrade vite-plus to enable
  native LSP" when the installed version is too old.
- Forward compatibility: future LSP-only features can raise the
  threshold by bumping the detector package, not by changing every
  consumer.

Changes:
- DetectResult gains optional vpVersion (set whenever vpPath is set).
- resolveVpAt() rejects installs whose package.json has no string
  version, treating them as orphan.
- Public API adds MIN_VP_VERSION_FOR_LSP constant and supportsLsp()
  helper for both consumers to use.
- Per-extension migration plan gains a fourth branch for the
  "installed but version too old" case.
- New conformance fixtures: installed-but-version-too-old,
  installed-no-version-field.
- New open question: the concrete value of MIN_VP_VERSION_FOR_LSP
  (TBD until #1557 ships).
826 lines → 302 lines. Reviewer flagged repetition and pushed back on
the version-gating mechanism. Changes:

- Drop MIN_VP_VERSION_FOR_LSP / supportsLsp / vpVersion entirely.
  Editors just attempt `vp lint --lsp`; if it fails on an old vp,
  the editor surfaces an upgrade hint at that point.
- Change "declared but not installed" UX: surface an install hint
  instead of silently falling back to plain oxlint. The reviewer is
  right that plain oxlint without VP_VERSION isn't Vite+-aware
  anyway, so silent fallback misleads more than it helps.
- Heavy trim of repetition: drop "Insight", drop the long
  bin-resolution code excerpts (kept brief file:line refs), fold
  "Why this rule" into the rule section, collapse the Decisions
  section, drop "Downstream coordination" (overlapped with the
  migration plan), drop the "Verification plan" subheading.
- Move "publish as a shared npm package?" from a locked Decision to
  an Open Question — the reviewer asked us to make the call; making
  it visibly open instead of locked invites the reviewer to weigh
  in.
- Shrink the conformance fixture table: drop entries that document
  non-features (global-vp-on-path, user-binpath-override) now that
  the rule plainly says we don't check $PATH or user settings, and
  drop the version-related fixtures.
Comment on lines +35 to +37
`dependencies` or `devDependencies`. A `node_modules/vite-plus/`
directory on its own does not qualify — that could be a transitive
install hoisted from an unrelated dependency tree.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A `node_modules/vite-plus/` directory on its own does not qualify — that could be a transitive install hoisted from an unrelated dependency tree.

I am not sure if vite-plus will ever be a dependency of another package. My naive brain does not like this.
VS Code Extension searched for package.json and tries to find node_modules/.bin/xyz.
Opening the package-file, transforming it to JSON, and checking its properties, will be done by every package.json it will find.
On a monorepo this could be expensive. 🤔

Comment on lines +64 to +66
Both phases stop AT the workspace root and never cross into its
parent. The walk-up bound is what prevents a nested checkout from
inheriting an unrelated parent's Vite+ install.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some users will open only a sub-package of a monorepo. Vite-plus from the root will not be detected.
Note: VS Code Extension does search outside the root (beside require.resolve), because mostly they are using Nested Multi Root Workspaces. But we should make sure setups like this will detect vite-plus


### Workspace root markers

A directory is a workspace root if any of the following is true:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Workspace roots are managed by the IDE itself. I do not think we should implement something new. (At least for VS Code).
Specially because users can add/remove workspaces roots from a single IDE session:
https://code.visualstudio.com/docs/editing/workspaces/multi-root-workspaces

For what is the workspace root marker useful?

`<vite-plus>/bin/oxlint` to `<vite-plus>/bin/vp`, update launch
args.

## Conformance fixtures
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens for workspaces where the root package.json does include oxlint, and only a sub-package includes vite-plus?
Should we prefer the vite-plus from the sub-package, even if other packages will use the roots oxlint?
Maybe related: oxc-project/oxc-vscode#145

Comment on lines +284 to +289
1. **Publish the detector as a shared npm package?** The current
proposal is `@voidzero-dev/detect-vite-plus` at
`packages/detect-vite-plus/`, consumed as a bundled devDependency
by `oxc-vscode` and `coc-oxc`. The alternative is to let each
Node-capable extension copy the ~50-line snippet directly.
Decision deferred to the maintainers.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The package makes sense for me. But I am not sure if this will be useful for other editors as they don't have JS runtime and needs to recreate it anyway by hand.


/** `bin/vp` exists AND the sibling package.json identifies as vite-plus. */
function resolveVpAt(dir: string): string | null {
const vpPath = join(dir, 'node_modules', 'vite-plus', 'bin', 'vp');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally we search for node_modules/.bin/oxlint and not node_modules/oxlint/bin/oxlint.
Is there a good reason to not target the .bin wrapper?

Comment on lines +137 to +139
- `$PATH`, user's global `node_modules`, or
`oxc.<tool>.binPath` settings (the last is for oxlint/oxfmt, not
`vp`).
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to search for global node_modules and $PATH variable too.
Users want a global editor formatter, which they can use without forcing the repo-dependency.

VP-Users needs to know that oxfmt is used and manual install it globally.

| `bin-vp-orphan` | Declared in root `package.json`, but `node_modules/vite-plus/` is broken (missing `package.json`, wrong `name`, or unparseable) | `{ root: "<repo>" }` — install rejected as orphan |
| `parent-vite-plus-nested-repo` | Outer dir declares + installs `vite-plus`; inner subdir is its own workspace root and does not | From inside the nested workspace: `null` |
| `plain-non-vite-plus` | A normal Node project, no `vite-plus` anywhere | `null` |
| `yarn4-pnp` | Berry/PnP, no `node_modules`, root `package.json` declares `vite-plus` | `{ root: "<repo>" }` — install hint |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants